// 
// Entify project
//  
// Author:
//       Lauri Taimila <lauri@taimila.com>
// 
// Copyright (c) 2010 Lauri Taimila
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Linq;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;

using Taimila.Entify.Attributes;

namespace Taimila.Entify
{
	/// <summary>
	/// Provides functionality to validate entities.
	/// </summary>
	public static class EntityValidator
	{
		/// <summary>
		/// Checks is given object type of valid entity type.
		/// </summary>
		/// <param name="entity">
		/// Object to check.
		/// </param>
		/// <returns>
		/// True if object is valid entity type, otherwise false. <see cref="System.Boolean"/>
		/// </returns>
		internal static bool IsValidEntityType<T>(T entity)
		{
			if(entity == null)
			{
				return false;
			}
			
			return EntityValidator.IsValidEntityType(typeof(T));
		}
		
		/// <summary>
		/// Checks is given type valid entity type.
		/// </summary>
		/// <param name="t">
		/// Type to be checked.
		/// </param>
		/// <returns>
		/// True if type is valid entity type, otherwise false.<see cref="System.Boolean"/>
		/// </returns>
		internal static bool IsValidEntityType(Type t)
		{
			if(t == null)
			{
				throw new ArgumentNullException();
			}
			
			if(AttributeReader.HasEntity(t))
			{
				int persistentPropertyCounter = 0;
				
				foreach(PropertyInfo pi in t.GetProperties())
				{
					if(AttributeReader.HasEntityProperty(pi))
					{
						persistentPropertyCounter++;
					}
				}
				
				if(persistentPropertyCounter > 0)
				{
					return true;
				}
			}
			
			return false;
		}
		
		/// <summary>
		/// Validate specified entities. Validation is made against validation
		/// rules specified for entity type with Validator attributes.
		/// </summary>
		/// <param name="entities">
		/// Entities to be validated. 
		/// </param>
		/// <returns>
		/// Validation results of each validated entity.
		/// </returns>
		public static ValidationResults Validate<T>(params T[] entities) where T : Entity
		{
			ValidationResults results = new ValidationResults();
			
			foreach(T entity in entities)
			{
				ValidationResult result = EntityValidator.ValidateEntity(entity);
				
				if(!result.IsValid)
				{
					results.AddResult(result);	
				}
			}
			
			return results;
		}
		
		/// <summary>
		/// Validate specified entities. Validation is made against validation
		/// rules specified for entity type with Validator attributes.
		/// </summary>
		/// <param name="entities">
		/// Entities to be validated. 
		/// </param>
		/// <returns>
		/// Validation results of each validated entity.
		/// </returns>
		public static ValidationResults Validate<T>(IEnumerable<T> entities) where T : Entity
		{
			if(entities == null)
			{
				throw new ArgumentNullException();	
			}
			
			return EntityValidator.Validate(entities.ToArray());
		}
		
		/// <summary>
		/// Validates one entity.
		/// </summary>
		/// <param name="entity">
		/// Entity to be validated.
		/// </param>
		/// <returns>
		/// ValidationResult of the entity. <see cref="ValidationResult"/>
		/// </returns>
		private static ValidationResult ValidateEntity<T>(T entity) where T : Entity
		{
			ValidationResult result = new ValidationResult(entity);
			var properties = from p in typeof(T).GetProperties() where AttributeReader.HasValidator(p) select p;
			var validationMethods = from m in typeof(T).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) where AttributeReader.IsValidatorMethod(m) select m;
			
			foreach(PropertyInfo property in properties)
			{
				foreach(var attribute in property.GetCustomAttributes(typeof(ValidatorAttribute), false))
				{
					object value = property.GetValue(entity, null);
					ValidationError error = (attribute as ValidatorAttribute).ValidateValue(property, value);
					
					if(error != null)
					{
						result.AddError(error);	
					}
				}
			}
			
			foreach(MethodInfo method in validationMethods)
			{

				if(method.ReturnParameter.ParameterType != typeof(ValidationError))
				{
					throw new InvalidOperationException("Validator method '" + method.Name + "' has invalid return type.");	
				}
				
				ValidationError error = (method.Invoke(entity, null)) as ValidationError;
				
				if(error != null)
				{
					result.AddError(error);
				}
			}
			
			return result;
		}
	}
}
